﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ArithmeticLibrary;
using GraphicLibrary;

namespace Drawing
{
    public partial class Form1 : Form
    {
        #region Fields

        FunctionDrawer drawer;

        /// <summary>
        /// Tabulka funkcnich hodnot funkce.
        /// </summary>
        FunctionPoint[] functionPoints;

        /// <summary>
        /// Tabulka bodu k vykresleni.
        /// </summary>
        Point[] points;

        /// <summary>
        /// Maximum x.
        /// </summary>
        double maxX;

        /// <summary>
        /// Maximum y.
        /// </summary>
        double maxY;

        /// <summary>
        /// Minimum x.
        /// </summary>
        double minX;

        /// <summary>
        /// Minimum y.
        /// </summary>
        double minY;

        /// <summary>
        /// Presnost vykreslovani.
        /// </summary>
        double precision;

        #endregion

        #region Constructors

        public Form1()
        {
            InitializeComponent();
        }

        #endregion // Constructors

        #region Form methods

        private void panel_Paint(object sender, PaintEventArgs e)
        {
            Expression exp;

            try
            {
                exp = new Expression(tbFunction.Text);
            }
            catch (Exception)
            {
                MessageBox.Show("Invalid expression.");
                return;
            }

            if (exp != null)
            {
                Function function = null;

                try
                {
                    function = new Function(exp);
                }
                catch (Exception)
                {
                    MessageBox.Show("Invalid function.");
                    return;
                }

                double start = 0; double end = 0;
                try
                {
                    start = double.Parse(tbStart.Text);
                    end = double.Parse(tbEnd.Text);

                    if (end <= start) throw new Exception();
                }
                catch (Exception)
                {
                    MessageBox.Show("Invalid interval.");
                    return;
                }

                FunctionDrawer drawer = new FunctionDrawer(e.Graphics, panel, function, start, end);

                drawer.DrawFunction();
            }

            
        }

        /*
        /// <summary>
        /// Vykresli funkci do panelu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void panel_Paint(object sender, PaintEventArgs e)
        {
            Graphics graph = e.Graphics;

            Expression exp;

            try
            {
                exp = new Expression(tbFunction.Text);
            }
            catch (Exception)
            {
                return;
            }

            if (exp != null)
            {
                Function function = null;

                try
                {
                    function = new Function(exp);
                }
                catch (Exception)
                {
                    MessageBox.Show("Invalid function.");
                    return;
                }

                // naplni tabulku hodnot
                if (!ProceedFunction(function))
                    return;
                try
                {
                    // vypocita z funkcnich hodnot body na vykresleni
                    GetPoints();

                    // nakresli osy x a y
                    DrawAxes(graph, 20, 15);

                    // nakresli bocni osy x a y
                    DrawSideAxes(graph, 20, 15);

                    // vykresli krivku
                    graph.DrawCurve(Pens.Red, points);
                }
                catch (Exception)
                {
                    MessageBox.Show("Cannot draw the function with the entered parameters.\n" +
                        "(The interval might be too wide or the functional value too great to draw.\n" +
                        "Also be mindful about the domain of definition.)");
                    return;
                }
            }
            else
            {
                return;
            }
        }
        */
        
        private void butDraw_Click(object sender, EventArgs e)
        {
            panel.Refresh();
        }

        #endregion // Form methods

        #region Private methods

        /// <summary>
        /// Zpracuje funkci. Vypocita funkcni hodnoty a ulozi je do tabulky hodnot.
        /// </summary>
        /// <param name="function">Funkce.</param>
        /// <param name="steps">Na kolik se ma rozdelit interval.</param>
        /// <returns>Vrati TRUE, pokud vsechno probehlo v poradku.</returns>
        private bool ProceedFunction(Function function)
        {
            try
            {
                maxX = double.Parse(tbEnd.Text);
                minX = double.Parse(tbStart.Text);

                // zkontrolujeme spravne zadany interval
                if (maxX <= minX)
                {
                    MessageBox.Show("Invalid interval.");
                    return false;
                }

                // automaticky nastavime presnost podle delky intervalu
                double range = maxX - minX;
                if (range <= 10) precision = 0.001;
                else if (range <= 100) precision = 0.001;
                else if (range <= 1000) precision = 0.01;
                else precision = 0.1;
            }
            catch (Exception)
            {
                MessageBox.Show("Invalid input values.");
                return false;
            }

            List<FunctionPoint> list = new List<FunctionPoint>();

            double x = minX;
            minY = function.Evaluate(x);
            maxY = function.Evaluate(x);

            while (x < maxX)
            {
                double y = function.Evaluate(x);
                
                list.Add(new FunctionPoint(x, y));

                if (y > maxY) maxY = y;

                if (y < minY) minY = y;
                
                x += precision;
            }

            functionPoints = list.ToArray();

            return true;

            // zaokrouhlime y-hodnoty
            //maxY = Math.Round(maxY);
            //minY = Math.Round(minY);
        }

        /// <summary>
        /// Vezme tabulku funkcnich hodnot a vypocita pro kazdou hodnotu bod na vykresleni.
        /// </summary>
        private void GetPoints()
        {
            points = new Point[functionPoints.Length];

            for (int i = 0; i < functionPoints.Length; i++)
            {
                points[i] = new Point(GetXCoordinate(functionPoints[i].X),
                    GetYCoordinate(functionPoints[i].Y));
            }
        }

        /// <summary>
        /// Priradi x-ove hodnote x-ovou souradnici pro vykresleni.
        /// </summary>
        /// <param name="x">Funkcni hodnota x.</param>
        /// <returns>Souradnici hodnoty x pro vykresleni.</returns>
        private int GetXCoordinate(double x)
        {
            //if (x < minX || x > maxX)
            //    throw new Exception("Chyba");

            // d ~ tolik pixelu na 1 bod x
            double d = (panel.Width - 80) / (Math.Abs(maxX - minX));

            return Convert.ToInt32(((x - minX) * d) + 40);
        }

        /// <summary>
        /// Priradi y-nove hodnote y-novou souradnici pro vykresleni.
        /// </summary>
        /// <param name="y">Funkcni hodnota y.</param>
        /// <returns>Souradnici hodnoty y pro vykresleni.</returns>
        private int GetYCoordinate(double y)
        {
            //if (y < minY || y > maxY)
            //    throw new Exception("Chyba");

            // d = tolik pixelu na 1 bod y
            double d = (panel.Height - 80) / (Math.Abs(maxY - minY));

            return Convert.ToInt32(panel.Height - 40 - ((y - minY) * d));
        }

        /// <summary>
        /// Nakresli osy x a y.
        /// </summary>
        /// <param name="graph">Objekt Graphic, ktery provadi samotne kresleni.</param>
        /// <param name="xSteps">Pocet dilku na ose x.</param>
        /// <param name="ySteps">Pocet dilku na ose y.</param>
        private void DrawAxes(Graphics graph, int xSteps, int ySteps)
        { 
            // pokud se nachazi nula na ose x
            if (maxX > 0 && minX < 0)
            {
                // nakreslime osu y
                graph.DrawLine(Pens.Black, GetXCoordinate(0), GetYCoordinate(maxY), 
                    GetXCoordinate(0), GetYCoordinate(minY));
            }

            // pokud se nachazi nula na ose y
            if (maxY > 0 && minY < 0)
            {
                // nakreslime osu x
                graph.DrawLine(Pens.Black, GetXCoordinate(maxX), GetYCoordinate(0),
                    GetXCoordinate(minX), GetYCoordinate(0));
            }
        }

        /// <summary>
        /// Nakresli bocni osy x a y.
        /// </summary>
        /// <param name="graph">Objekt Graphic, ktery provadi samotne kresleni.</param>
        /// <param name="xSteps">Pocet dilku na ose x.</param>
        /// <param name="ySteps">Pocet dilku na ose y.</param>
        private void DrawSideAxes(Graphics graph, int xSteps, int ySteps)
        {
            // dolni osa x
            graph.DrawLine(Pens.Gray, 20, panel.Height - 20, panel.Width - 20, panel.Height - 20);

            // leva osa y
            graph.DrawLine(Pens.Gray, 20, 20, 20, panel.Height - 20);

            System.Drawing.Font font = new Font("Consolas", 10);
            Brush brush = new SolidBrush(Color.Gray);
            RectangleF rectangle;

            int[] x = new int[xSteps];
            double step = Math.Abs(maxX - minX) / xSteps;
            for (int i = 0; i < x.Length; i++)
            {
                x[i] = Convert.ToInt32(step * i + minX);
            }

            for (int i = 0; i < x.Length; i++)
            {
                graph.DrawLine(Pens.Gray, GetXCoordinate(x[i]), panel.Height - 25,
                    GetXCoordinate(x[i]), panel.Height - 20);

                rectangle = new RectangleF(GetXCoordinate(x[i]) - 5, panel.Height - 20, 50, 15);

                graph.DrawString(x[i].ToString(), font, brush, rectangle);
            }


            int[] y = new int[ySteps];
            step = Math.Abs(maxY - minY) / ySteps;

            for (int i = 0; i < y.Length; i++)
            {
                y[i] = Convert.ToInt32(step * i + minY);
            }

            for (int i = 0; i < y.Length; i++)
            {
                graph.DrawLine(Pens.Gray, 20, GetYCoordinate(y[i]), 25, GetYCoordinate(y[i]));
                rectangle = new RectangleF(30, GetYCoordinate(y[i]) - 7, 50, 15);
                graph.DrawString(y[i].ToString(), font, brush, rectangle);
            }

            
        }

        #endregion

    }
}
